﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AlgoImpl.Constraints;

namespace AlgoImpl
{
    public class Algorithm
    {
        private const int k_IndexOfWorker = 1;
        private const int k_IndexOfShift = 2;
        private const int k_NotValidForRegistrationWeight = -1;


        public void RunAlgo()
        {
            bool toContinue = true;
            while (toContinue)
            {
                toContinue = runOneRoundOfAlgo();
                Graph.Instance.InitLeftAndRightNodes();  
            }
        }

        private List<Node> bfs()
        {
            Queue<Node> q = new Queue<Node>();
            MyArray<int> levels = new MyArray<int>(Graph.Instance.Nodes.Count + 2, -1);
            MyArray<Node> path = new MyArray<Node>(Graph.Instance.Nodes.Count + 2, null);
            q.Enqueue(Graph.Instance.Source);
            levels.AddAt("s", 0);

            while (q.Count > 0)
            {
                Node u = q.Dequeue();
                foreach (Nieghbour v in u.Neighbours)
                {
                    if (levels.GetAt(v.Id) == -1)
                    {
                        if (v.Weight > 0)
                        {
                            levels.AddAt(v.Id, (levels.GetAt(u.Id) + 1));
                            path.AddAt(v.Id, Graph.Instance[u.Id]);
                            q.Enqueue(Graph.Instance[v.Id]);
                        }
                    }
                }
            }
            Node lastInRoute = getLastNodeInRoute(path);
            return getRoute(lastInRoute, path);
        }
        private bool runOneRoundOfAlgo()
        {
            bool continueRound = true;
            bool isThereImprovingPathInGraph = false;
            while (Graph.Instance.Source.IsHaveNeighboursWithPositiveWeight() && continueRound)
            {
                List<Node> oneRoute = bfs();
                if (isRouteCompleted(oneRoute))
                {
                    handleWithCompletedRoute(oneRoute);
                    isThereImprovingPathInGraph = true;
                }
                else
                {
                    continueRound = false;
                }
            }
            return isThereImprovingPathInGraph;
        }
        private void handleWithCompletedRoute(List<Node> oneRoute)
        {
            registerWorkersToShiftsInRoute(oneRoute);  
        }

        private bool isRouteCompleted(List<Node> route)
        {
            return route.Count == 4;
        }
        private void registerWorkersToShiftsInRoute(List<Node> route)
        {
            string shiftIdToRegisterTo = (route[k_IndexOfShift] as DataNode).Data;
            string workerIdWantToRegister = (route[k_IndexOfWorker] as DataNode).Data;
            if (isPosiblleToRegisterWorkerToShift(shiftIdToRegisterTo, workerIdWantToRegister))
            {
                SharedData.Instance.GetShiftById(shiftIdToRegisterTo).AddWorker(workerIdWantToRegister);
                SharedData.Instance.GetWorkerById(workerIdWantToRegister).AddShift(shiftIdToRegisterTo);
                decreaseWeightAlongRoute(route);
            }
            else
            {
                setRouteAsNotValidForRegistration(route);
            }
        }
        private void decreaseWeightAlongRoute(List<Node> oneRoute)
        {
            for (int i = 0; i < oneRoute.Count; i++)
            {
                if (oneRoute[i].IsHaveNeighbours())
                {
                    oneRoute[i].DecreaseWeightOfNaighbour(oneRoute[i + 1].Id);
                }
            }
        }
        private void setRouteAsNotValidForRegistration(List<Node> oneRoute)
        {
            for (int i = 0; i < oneRoute.Count; i++)
            {
                if (oneRoute[i].IsHaveNeighbours())
                {
                    oneRoute[i].SetWeightOfNaighbour(oneRoute[i + 1].Id, k_NotValidForRegistrationWeight);
                }
            }
        }
        private Node getLastNodeInRoute(MyArray<Node> path)
        {
            Node lastNode = null;
            if(path.GetAt("t") != null)
            {
                lastNode = Graph.Instance.Target;
            }
            else // route didn't finished
            {
                for (int i = path.Size - 3; i >= 0; i--)
                {
                    if (path.GetAt(i.ToString()) != null)
                    {
                        lastNode = Graph.Instance.Nodes[i];
                        break;
                    }
                }
             }
            return lastNode;
        }
        private List<Node> getRoute(Node endOfRoute, MyArray<Node> arrayOfRoute)
        {
            List<Node> route = new List<Node>();
            getPath(endOfRoute, arrayOfRoute, ref route);

            return route;
        }
        private void getPath(Node node, MyArray<Node> path, ref List<Node> oneRoute)
        {
            if (path.GetAt(node.Id) != null)
            {
                getPath(path.GetAt(node.Id), path, ref oneRoute);
            }
            oneRoute.Add(node);
        }    
        private void printRoute(List<Node> route)
        {
            foreach (Node node in route)
            {
                Console.WriteLine(node.ToString());
            }
        }
        private bool isPosiblleToRegisterWorkerToShift(string shift, string worker)
        {
            bool isPosible = true;
            ConstraintsChecker.Instance.InitializeWorkerAndShift(worker, shift);
            if (!ConstraintsChecker.Instance.CheckAllConstraints(null))
            {
                isPosible = false;
            }
            return isPosible;
        }

        private class MyArray<T>
        {
            private List<T> m_List;
            int m_Size;

            public MyArray(int size, T initializeValue)
            {
                m_List = new List<T>(size);
                m_Size = size;
                for (int i = 0; i < m_Size; i++)
                {
                    m_List.Add(initializeValue);
                }
            }

            public int Size
            {
                get { return m_Size; }
            }

            public int Count
            {
                get { return m_List.Count; }
            }

            public T GetAt(string sOrt)
            {
                if (sOrt.Equals("s"))
                {
                    return m_List[Size - 2];
                }
                else if (sOrt.Equals("t"))
                {
                    return m_List[Size - 1];
                }
                else
                {
                    return m_List[int.Parse(sOrt)];
                }
            }

            public void AddAt(string str, T data)
            {
                if (str.Equals("s"))
                {
                    m_List[Size - 2] = data;
                }
                else if (str.Equals("t"))
                {
                    m_List[Size - 1] = data;
                }
                else
                {
                    m_List[int.Parse(str)] = data;
                }
            }

            public void Add(T data)
            {
                m_List.Add(data);
            }

            public void Print()
            {
                for (int i = 0; i < m_Size; i++)
                {
                    Console.WriteLine(i + "--> " + m_List[i]);
                }
            }
        }
    }
}
